﻿using System;
using System.Collections.Generic;
using System.Linq;
using DeSleeper.Client.Installation;
using DeSleeper.Client.Properties;
using DeSleeper.Client.WakeUp;
using DeSleeper.Library;
using NLog;

namespace DeSleeper.Client.Service
{
    public class InstallServiceCommand : ServiceCommand
	{
        private static readonly Logger _logger = LogManager.GetCurrentClassLogger();

	    public List<Exception> Errors { get; set;}
        public InstallServiceCommand ()
        {
            Errors = new List<Exception>();
        }

		public override string Text { get { return "install"; }}

		/// <exception cref="ArgumentException">
		/// <see cref="InstallServiceCommand"/> requires a <see cref="InstallServiceParameters"/> parameter.
		/// </exception>
		protected override void OnExecute(object parameter)
		{
			var installServiceParameters = parameter as InstallServiceParameters;
			if (installServiceParameters == null)
				throw new ArgumentException(Resources.ErrorRequiresInstallServiceParameters);

		    var targets = installServiceParameters.InstallTargets;
		    if (targets.IncludesLocal)
			    installLocal(installServiceParameters);

		    installRemote(installServiceParameters);

            foreach (var installTarget in targets.CompatibleAndUnknownRemote)
            {
                var localDestination = new ProxyWakeUpDestination { ProxyAddress = installTarget };
                precacheHosts(installServiceParameters.PrecacheHostNames, localDestination);
            }
		}

	    private void installLocal(InstallServiceParameters installServiceParameters)
	    {
	        var installer = new Installer();
	        if (installServiceParameters.IsInstalled)
	            installer.UninstallService();

	        installer.InstallService(installServiceParameters.Options);
	        installer.StartService();
	    }

        private void installRemote(InstallServiceParameters installServiceParameters)
        {
            var exceptions = new List<Exception>();
            var installer = new Installer();

            var targets = installServiceParameters.InstallTargets;
            IEnumerable<string> compatibleRemote;
            if (installServiceParameters.SuppressCheckForDotNet)
            {
                compatibleRemote = targets.CompatibleAndUnknownRemote;
            }
            else
            {
                compatibleRemote = targets.CompatibleRemote;
                exceptions.AddRange(targets.CategorizationErrors.Values);
            }

            foreach (var machineName in compatibleRemote)
            {
                try
                {
                    if (Installer.IsInstalled(machineName))
                        installer.UninstallService(machineName);

                    installer.StageServiceFiles(machineName);
                    installer.InstallService(machineName,
                                             installServiceParameters.Options);
                    installer.StartService(machineName);
                }
                catch (Exception e)
                {
                    addException(exceptions, machineName, e);
                }
            }

            if (targets.IncompatibleRemote.Count() > 0)
            {
                exceptions.Add(
                    new InvalidOperationException(
                        string.Format(
                            ".NET 3.5 SP1 must be installed before remotely deploying, but was not on the following machines:  {0}.",
                            string.Join(", ", targets.IncompatibleRemote.ToArray()))));
            }
            if (exceptions.Count == 1)
                throw new SelfFlatteningException(string.Empty, exceptions[0]);
            else if (exceptions.Count > 1)
                throw new SelfFlatteningAggregateException(exceptions);
        }

	    private void addException(ICollection<Exception> exceptions, string machineName, Exception exception)
	    {
	        exceptions.Add(new SelfFlatteningException(
	                           string.Format(
	                               @"An error occurred while attempting to install the deSleeper service on {0}.",
	                               machineName)
	                           ,exception));
	    }

	    private void precacheHosts(IEnumerable<string> precacheHostNames, ProxyWakeUpDestination localDestination)
	    {
	        foreach (var hostName in precacheHostNames)
	        {
	            try
	            {
	                var target = new TargetComputer
	                                 {Destination = localDestination, WakeUpMethod = new HostNameWakeUpMethod()};
	                HostNameWakeUpMethod.SetHostName(target, 
                        string.Equals(hostName, "localhost", StringComparison.InvariantCultureIgnoreCase) ?
                        Environment.MachineName : hostName );

	                if (target.WakeUpMethod.IsValid)
	                    target.WakeUp();

	                if (string.Equals(localDestination.ProxyAddress, "localhost", StringComparison.InvariantCultureIgnoreCase))
	                {
	                    if (!TargetComputer.All.Any(oldTarget =>
	                                                string.Equals(HostNameWakeUpMethod.GetHostName(oldTarget), hostName,
	                                                              StringComparison.InvariantCultureIgnoreCase)))
	                    {
	                        TargetComputer.All.Add(target);
	                    }
	                }
	            }
	            catch (Exception e)
	            {
	                _logger.ErrorException(
	                    string.Format("Precache of host {0} failed", hostName),
	                    e);
	                Errors.Add(e);
	            }
	        }
	    }
	}
}
